#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include <stdio.h>
#include <cstdlib>

#include "Tokenizer.h"
#include "Image.h"

#include "objects/GeometryObject.h"
#include "objects/Cone.h"
#include "objects/Sphere.h"
#include "objects/Plane.h"
#include "objects/Camera.h"
#include "objects/Box.h"
#include "objects/Triangle.h"
#include "objects/LightSource.h"

using namespace std;

vector<GeometryObject *> objects;
Camera camera;

void ReadFile(const char* name) {
    vector<string> list;
    string line;
    char ch;
    int nestedBraces = 0;

    ifstream myfile(name);

    if (myfile.is_open()) {
        while(!myfile.eof()) {
	        myfile.get(ch);

	        if (ch == '\n' || ch == '\r') {
	            if (line[0] == '/' && line[1] == '/') {
	                cout << "Comment found, throwing line away..." << endl;
	                line = "";
	            }
	        }
	        else if (ch == '{') {
	            nestedBraces++;
	        }
	        else if (ch == '}') {
	            nestedBraces--;
	            
	            // If the nested braces are ended, process the data elements
	            if (nestedBraces == 0) {
	                list = TokenizeLine(line, " <>,");
	            
	                if (list[0] == "camera") {
	                    Camera c (list);
	                    cout << "Reading in camera" << endl;
	                    camera = c;
	                }
	                else if (list[0] == "light_source") {
	                    LightSource ls (list);
	                    cout << "Reading in light source" << endl;
	                }
	                else if (list[0] == "sphere") {
	                    cout << "Reading in sphere" << endl;
                        GeometryObject *s = new Sphere(list);
                        objects.push_back(s);
                    }
                    else if (list[0] == "plane") {
                        cout << "Reading in plane" << endl;
                        GeometryObject *p = new Plane(list);
                        objects.push_back(p);
                    }
                    else if (list[0] == "triangle") {
                        Triangle t (list);
                        cout << "Reading in triangle" << endl;
                    }
                    else if (list[0] == "box") {
                        Box b (list);
                        cout << "Reading in box" << endl;
                    }
                    else if (list[0] == "cone") {
                        Cone c (list);
                        cout << "Reading in cone" << endl;
                    }
	                else {
	                    for (int i = 0; i < list.size(); i++) {
	                        cout << list[i] << endl;
	                    }
	                }
	                
	                line = "";
	            }
	        }
	        else {
	            line = line + ch;
	        }
	    }
    }
    else {
        printf("Error opening file\n");
    }
    
    printf("Read in %d objects\n", objects.size());
    
}

Color trace(vec3 &rayOrigin, vec3 &rayDirection) {
    float tnear = INFINITY;
    GeometryObject* obj = NULL;

    // find intersection of this ray with the sphere in the scene
    for (uint i = 0; i < objects.size(); ++i) {
        float t0 = INFINITY, t1 = INFINITY;
        if (objects[i]->intersect(rayOrigin, rayDirection, &t0, &t1)) {
            if (t0 < 0) {
                t0 = t1;
            }
           
            if (t0 < tnear) {
                if (tnear == INFINITY) {
                    tnear = t0;
                    obj = objects[i];
                } 
            }
        }
    }

    // if there's no intersection return black or background color
    if (!obj) {
        return Color(2.0f);
    }
    else {
        return obj->surfaceColor;
    }
}

void Render(char* inputFile, int height, int width) {
    Image output(width, height);
	float invWidth = 1 / ((float)width);
	float invHeight = 1 / ((float)height);
	float fov = 30;
	float aspectratio = width / ((float)height);
	float angle = tan(M_PI * 0.5 * fov / 180.0f);

    for (uint y = 0; y < height; y++) {
        for (uint x = 0; x < width; x++) {
            float rayX = (2 * ((x + 0.5) * invWidth) - 1) * angle * aspectratio;
			float rayY = (1 - 2 * ((y + 0.5) * invHeight)) * angle;
			vec3 direction(rayX, rayY, -1);
			vec3 origin = camera.location;
			
			/* Get the raytracer color */
			direction = glm::normalize(direction);
			Color rayColor = trace(origin, direction);
			
			/* Write color to image */
			color_t pix = output.pixel(x, y);
			pix.r = rayColor.r;
			pix.g = rayColor.g;
			pix.b = rayColor.b;
			output.pixel(x, y, pix);
        }
    }
    
    int length = strlen(inputFile);
    char* outputName = (char* )malloc(sizeof(char) * length);
    strncpy(outputName, inputFile, length - 3);
    
    outputName[length - 3] = 't';
    outputName[length - 2] = 'g';
    outputName[length - 1] = 'a';
    
    output.WriteTga(outputName, true);
    
}

int main(int argc, char** argv) {
    int height, width;
    char* inputFile;
    
    if (argc != 4 && argc != 5) {
        printf("raytrace: imageWidth imageHeight input-file\n");
        return 0;
    }
    else if (argc == 4) {
        width = strtol(argv[1], NULL, 10);
        height = strtol(argv[2], NULL, 10);
        inputFile = argv[3] + 2;
    }
    else if (argc == 5) {
        width = strtol(argv[1], NULL, 10);
        height = strtol(argv[2], NULL, 10);
        inputFile = argv[4];
    }
    
    ReadFile(inputFile);
    Render(inputFile, height, width);
    
    return 0;
}
